package com.pajk.user.service.user;

import com.alibaba.common.lang.StringUtil;
import com.pajk.commons.types.VersionedValue;
import com.pajk.user.ExceptionCode;
import com.pajk.user.api.*;
import com.pajk.user.dal.index.Index;
import com.pajk.user.dal.mapper.*;
import com.pajk.user.dal.model.AccountChannelDO;
import com.pajk.user.dal.model.UserDO;
import com.pajk.user.dal.model.UserDeviceBindingDO;
import com.pajk.user.dal.model.UserExtDO;
import com.pajk.user.dal.model.enums.AccountChannelStatus;
import com.pajk.user.exception.*;
import com.pajk.user.model.InviteInfo;
import com.pajk.user.model.InviteeFolloweeInfo;
import com.pajk.user.model.UserRelation;
import com.pajk.user.model.enums.ChannelType;
import com.pajk.user.model.enums.RelationType;
import com.pajk.user.model.User;
import com.pajk.user.model.enums.UserStatus;
import com.pajk.user.service.common.idpool.IDPool;
import com.pajk.user.service.credit.CreditServiceWlt;
import com.pajk.user.service.login.sms.SmsPasswordManager;

import net.pocrd.core.ServiceException;
import net.pocrd.dubboext.DubboExtProperty;
import net.pocrd.entity.CallerInfo;
import net.pocrd.entity.ReturnCode;

import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.omg.CORBA.UserException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import java.sql.Date;
import java.util.*;
import java.util.regex.Pattern;

import javax.annotation.Resource;

/**
 * @author Haomin Liu
 */

public class UserServiceImpl implements UserService {

    private static final Logger Log = LoggerFactory.getLogger(UserServiceImpl.class);

    private static final String READONLY_ACCESS_FOR_ALLFIELDS = "{\"*\":\"R\"}";
    private static final String READWRITE_ACCESS_FOR_ALLFIELDS = "{\"*\":\"R|W\"}";

    @Resource
    UserMapper userMapper;

    @Resource
    AccountChannelMapper accountChannelMapper;

    @Resource
    UserRelationMapper userRelationMapper;

    @Resource
    UserReverseRelationMapper userReverseRelationMapper;

    @Resource
    Index<String, UserRelation> userReverseRelationIndex;

    @Resource
    UserExtMapper userExtMapper;

    @Resource
    Index<String, Long> weixinOpenIdToUserIdIndex;

    @Resource
    Index<Long, Long> mobileToUserIdIndex;

    @Resource
    Index<String, Long> wanliToUserIdIndex;

    @Resource
    Index<String,HashMap<Long,InviteInfo>> inviteeMobileToInviteInfoIndex;

    @Resource
    Index<Long,List<String>> inviteListIndex;


    @Resource
    CreditServiceWlt creditServiceWlt;

    @Resource
    PlatformTransactionManager transactionManager;

    @Resource
    IDPool userIdPool;
    
    @Resource
	UserDeviceBindingMapper userDeviceBindingMapper;
    
    @Resource
    SmsPasswordManager smsPasswordManager;
    
    @Resource
    SqlSessionFactory sqlSessionFactory;
    

    @Override
    public User getUserById(Long userId) {
        try{
            User user = userMapper.getUserById(userId);
            if(user != null){
                return user;
            }
        }catch(Throwable t){
            Log.error(null, t);
            throw new UserServiceException(t, ExceptionCode.InternalSystemErrorException.getCode());
        }
        return null;
    }

    @Override
    @Transactional
    public User getUserByOpenId(String openId, ChannelType from) {
        if(!ChannelType.WEIXIN.equals(from)){
            throw new IllegalArgumentException(String.format("Unsupported channeltype! channeltype=%s", from));
        }

        if(openId == null){
            throw new IllegalArgumentException("OpenId cannot be null!");
        }

        try{
            Long userId = weixinOpenIdToUserIdIndex.get(openId);

            if(userId == null){
                return null;
            }

            User user = userMapper.getUserById(userId);
            if(user == null){
                throw new UserServiceException(String.format("Dirty Data: AccountChannel(%s, %s) found. But corresponding User(%d) not found!",
                        openId, from, userId), ExceptionCode.InternalSystemErrorException.getCode());
            }
            Log.info("getUserByOpenId: (OpenId={}, channel={}) ==> userid={}, found", openId, from, userId);
            return user;
        }catch(UserServiceException e){
            // own exception, re-throw
            Log.error("UserServiceException catched.", e);
            throw e;
        }catch(RuntimeException re){
            Log.error("RuntimeException catched.", re);
            throw new UserServiceException(re, ExceptionCode.InternalSystemErrorException.getCode());
        }catch(Throwable t){
            Log.error("Throwable catched.", t);
            throw new UserServiceException(t, ExceptionCode.InternalSystemErrorException.getCode());
        }
    }

    @Override
    @Transactional
    public void updateUserInfo(Long userId, User changes){
        try{
            Map<String, Object> params = convert(changes);
            params.put("id", userId);
            userMapper.updateUserById(params);
            Log.info("updateUserInfo: update user({}) with properties: {}", userId, params);
        }catch(UserServiceException e){
            // own exception, re-throw
            Log.error("UserServiceException catched.", e);
            throw e;
        }catch(Throwable t){
            Log.error("Throwable catched.", t);
            throw new UserServiceException(t, ExceptionCode.InternalSystemErrorException.getCode());
        }
    }
    
    @Override
    @Transactional
    public void updateUserCredits(Long userId, int credit){
        try{
            Map<String, Object> params = new HashMap<String, Object>();
            params.put("credits", credit);
            params.put("id", userId);
            userMapper.updateUserCreditById(params);
            Log.info("updateUserCredits: add credits({}) to user({})", credit, userId);
        }catch(UserServiceException e){
            // own exception, re-throw
            Log.error("UserServiceException catched.", e);
            throw e;
        }catch(Throwable t){
            Log.error("Throwable catched.", t);
            throw new UserServiceException(t, ExceptionCode.InternalSystemErrorException.getCode());
        }
        
    }
    
    @Override
    @Transactional
    public Boolean updateMobile(Long userId, Long mobile) {
        try{
            Log.info("updateMobile: Updating user({}) with mobile({})", userId, mobile);
            // check if mobile is used by others
            Long id = mobileToUserIdIndex.get(mobile);

            // passed the same phone, do nothing
            if((id != null) && (id.equals(userId))){
                return true;
            }

            if ((id != null) && id != userId){
                throw new UserServiceException(String.format("mobile(%d) is already used by User(%d)!", mobile, id),
                                               ExceptionCode.UniqueKeyViolationException.getCode());
            }
            // check if there is already an old mobile binded
            User user = userMapper.getUserById(userId);
            Long oldMobile = user.getMobile();

            // do db update
            Map<String, Object> param = new HashMap<String, Object>();
            param.put("id", userId);
            param.put("mobile", mobile);
            userMapper.updateUserById(param);

            // do mobileIndex update
            //
            // CAUTION: THIS STEP SHOULD BE PLACED AFTER DB UPDATE, so that this step is natually skipped if DB exception happens.
            // and, because this step ensures eventual consistency, we have no need to do rollbackTimesFromTair if this steps fails for the moment.
            mobileToUserIdIndex.put(mobile, userId);
            if(oldMobile != null){
                mobileToUserIdIndex.remove(oldMobile);
                Log.info("updateMobile.change {}:{} ",userId,mobile);
            }else{
                //first time bind mobile,create Temp WLT account,then set user wanliId
                String actionId = UUID.randomUUID().toString();
                String wanliId=creditServiceWlt.createTempAccount(String.valueOf(userId),actionId);
                param.put("wanliId", wanliId);
                userMapper.updateUserById(param);
                wanliToUserIdIndex.put(wanliId,userId);
                Log.info("updateMobile.bind {}:{},wanliId:{} ",userId,mobile,wanliId);
            }   
        }catch(UserServiceException e){
            // own exception, re-throw
            Log.error("UserServiceException catched.", e);
            throw e;
        }catch(Throwable t){
            Log.error("Throwable catched.", t);
            throw new UserServiceException(t, ExceptionCode.InternalSystemErrorException.getCode());
        }
        return true;
    }

    @Override
    public User getUserByMobile(Long mobile) {
        if(mobile == null){
            throw new IllegalArgumentException("mobile cannot be null!");
        }

        try{
            Long userId = mobileToUserIdIndex.get(mobile);
            if (userId == null){
                return null;
            }

            User user = userMapper.getUserById(userId);
            if (user == null){
                String msg = String.format("Dirty data! mobile(%d)-->userId(%d) is found in mobileIndex, but user entity is not found in DB!", mobile, userId);
                Log.error(msg);
                throw new UserServiceException(msg, ExceptionCode.InternalSystemErrorException.getCode());
            }
            Log.info("getUserByMobile: mobile({}) ==> user({}) found!", userId, mobile);
            return user;
        }catch(UserServiceException e){
            // own exception, re-throw
            Log.error("UserServiceException catched.", e);
            throw e;
        }catch(Throwable t){
            Log.error("Throwable catched.", t);
            throw new UserServiceException(t, ExceptionCode.InternalSystemErrorException.getCode()); 
        }
    }

    @Override
    @Transactional
    public User getOrCreateUserByOpenId(String openId, ChannelType from) {

        if((openId == null)||(from == null)){
            throw new IllegalArgumentException(String.format("input parameter cannot be null! openId = %s, from = %s", openId, from));
        }

        if(from != ChannelType.WEIXIN){
            throw new IllegalArgumentException(String.format("Only WEIXIN channel type is supported for now! ChannelType received = %s", from));
        }

        if(openId.length() > 64){
            throw new IllegalArgumentException("Open ID length exceeded 64 chars!");
        }

        String borrowedUserID = null;
        Boolean success = false;
        // get user first
        try{
            Long userId = weixinOpenIdToUserIdIndex.get(openId);

            if(userId != null){
                User user = userMapper.getUserById(userId);
                if(user == null){
                    String msg = String.format("Dirty data! openId(%s)-->userId(%d) is found in weixinIndex, but user entity is not found in DB!", openId, userId);
                    Log.error(msg);
                    throw new UserServiceException(msg, ExceptionCode.InternalSystemErrorException.getCode());
                }
                Log.info("getOrCreateUserByOpenId: (openId({}), channel({})) ==> user({}) found! user obj returned!", openId, from, user);
                return user;
            }

            // 1. create entity in User table
            UserDO userDo = new UserDO();
            borrowedUserID = userIdPool.borrow();

            userDo.setUserStatus(UserStatus.VALID);
            userDo.setId(Long.valueOf(borrowedUserID));
            userDo.setBirthday(new Date(70, 0, 1));
            userMapper.insertUser(userDo);


            // 2. create entity in AccountChannel table
            AccountChannelDO acc = new AccountChannelDO(openId, from, userDo.getId(), AccountChannelStatus.VALID);
            accountChannelMapper.insertAccountChannel(acc);

            // 3. insert openId->UserId into reverse index.
            //
            // CAUTION: THIS STEP SHOULD BE PLACED AFTER DB UPDATE, so that this step is natually skipped if DB exception happens.
            // and, because this step ensures eventual consistency, we have no need to do rollbackTimesFromTair if this steps fails for the moment.
            weixinOpenIdToUserIdIndex.put(openId, userDo.getId());

            // mark whole process done
            success = true;
            Log.info("getOrCreateUserByOpenId: new user({}) created for openId({}) from channel({})", userDo.getId(), openId, from);
            return new User(userDo);
        }catch(UserServiceException e){
            // own exception, re-throw
            Log.error("userservice exception:", e);
            throw e;
        }catch(Throwable t){
            Log.error("unknown exception catched!", t);
            throw new UserServiceException(t, ExceptionCode.InternalSystemErrorException.getCode());
        }finally {
            // return or consume id
            try{
                if(success){
                    if(borrowedUserID != null){
                        userIdPool.consume(borrowedUserID);
                    }
                }else{
                    if(borrowedUserID != null){
                        userIdPool.giveback(borrowedUserID);
                    }
                }
            }catch(Throwable t){
                Log.warn(String.format("failed to return/consume id from id pool! userId=%s, sucess=%s",
                        borrowedUserID, success), t);
            }
        }

    }

    @Override
    public User createUser(User user) {
        UserDO userDo = new UserDO(user);
        userDo.setUserStatus(UserStatus.VALID);
        userDo.setId(Long.valueOf(userIdPool.get()));
        userMapper.insertUser(userDo);
        Log.info("createNewUser: userId {}",userDo.getId());
        return new User(userDo);
    }

    @Override
    @Transactional
    public void mergeAccountChannel(Long oldUserId, Long newUserId) {
        try{
            List<AccountChannelDO> fromAccounts = accountChannelMapper.getAccountChannelByUserId(oldUserId);
            List<AccountChannelDO> toAccounts = accountChannelMapper.getAccountChannelByUserId(newUserId);

            Set<ChannelType> channelCounter = new HashSet<ChannelType>();
            Boolean ret = checkChannelUniqueness(fromAccounts, channelCounter);
            Boolean ret1 = checkChannelUniqueness(toAccounts, channelCounter);

            if((ret == false)||(ret1 == false)){
                throw new UserServiceException(String.format("Can't merge accounts from User(%d) to User(%d)", oldUserId, newUserId),
                                               ExceptionCode.UniqueAccountPerChannelViolationException.getCode());
            }

            List<String> fromOpenIds = new ArrayList<String>();
            for(AccountChannelDO acc : fromAccounts){
                fromOpenIds.add(acc.getOpenId());
            }

            // 1. update DB first
            accountChannelMapper.batchUpdateAccountUserIdByUserId(oldUserId, newUserId);

            // 2. update reverse index
            //
            // CAUTION: THIS STEP SHOULD BE PLACED AFTER DB UPDATE, so that this step is natually skipped if DB exception happens.
            // and, because this step ensures eventual consistency, we have no need to do rollbackTimesFromTair if this steps fails for the moment.
            //
            // TODO complete mlock and munlock interface
            //try{
            //  weixinOpenIdToUserIdIndex.mlock(fromOpenIds);
            for(String openId : fromOpenIds){
                weixinOpenIdToUserIdIndex.put(openId, newUserId);
            }
            //finally{
            //  weixinOpenIdToUserIdIndex.munlock(fromOpenIds);
            //}
        }catch(UserServiceException e){
            // own exception, re-throw
            Log.error(null, e);
            throw e;
        }catch(Throwable t){
            Log.error(null, t);
            throw new UserServiceException(t, ExceptionCode.InternalSystemErrorException.getCode());
        }
    }

    @Override
    @Transactional
    public Boolean bindWltAccount(Long userId, String wltAccountId) {

        if(wltAccountId.length() > 32){
            throw new ServiceException(ReturnCode.PARAMETER_ERROR,
                    String.format("wanli id(%s) too long!", wltAccountId));
        }

        try{
            Long wanliUserId=wanliToUserIdIndex.get(wltAccountId);

            User user = userMapper.getUserById(userId);
            if(user==null){
                throw new ServiceException(UserServiceHttpCode.USER_NOT_FOUND,
                        String.format("USER_ID(%s) not found ", userId));

            }
            if((user != null) && (user.getId().equals( wanliUserId) )){
                return true; // no need update
            }

            if((user != null && wanliUserId != null) && (!user.getId().equals(wanliUserId))){
                throw new ServiceException(UserServiceHttpCode.WANLIID_BINDED_TO_DIFFERENT_USER,
                                           String.format("WANLI_ID(%s) already binded to another user(%d)", wltAccountId, user.getId()));
            }

            // Do update
            Map<String, Object> params = new HashMap<String, Object>();
            params.put("id", userId);
            params.put("wanliId", wltAccountId);

            userMapper.updateUserById(params);

            //replace by new wltAccountId
            String originWalLiId=user.getWanliId();
            if(originWalLiId!=null){
                wanliToUserIdIndex.remove(originWalLiId);
            }

            wanliToUserIdIndex.put(wltAccountId,userId);
            Log.info("bindWltAccount: userId({}) => wltAccountId({}) binded", userId, wltAccountId);
            return true;
        }catch(ServiceException e){
            Log.error(null, e);
            throw e;
        }
        catch(Throwable t){
            Log.error(null, t);
            throw new ServiceException(ReturnCode.INTERNAL_SERVER_ERROR, "unknown error!");
        }
    }

    @Override
    @Deprecated
    public long createFollowee(Long followerId, User followee, RelationType followReason, String nick) {
        return createFollowee( followerId,  followee,  followReason,  nick,  "weixin");
    }

    @Override
    public long createFollowee(Long followerId, User followee, RelationType followReason, String nick, String appFrom) {
        Log.info("createFollowee: followerId={}, Relationtype={} with relationNick={}, from app={}, followee={}",
                followerId, followReason, nick, appFrom, followee);
        long followeeId = 0;
        // the followee must not exist
        if(followee.getId() != null){
            throw new IllegalArgumentException(String.format("Cannot create an existing user(userId = %d)! ", followee.getId()));
        }

        try{
            //check if followerId exist
            checkUserIdExists(followerId);

            String borrowedUserID=userIdPool.borrow();
            followeeId=Long.valueOf(borrowedUserID);
            UserDO followeeDO=new UserDO(followee);
            followeeDO.setId(followeeId);

            DefaultTransactionDefinition def=new DefaultTransactionDefinition();
            def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
            def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
            TransactionStatus status=transactionManager.getTransaction(def);
            try{
                // insert user
                userMapper.insertUser(followeeDO);
                transactionManager.commit(status);
                userIdPool.consume(borrowedUserID);
            }catch(Exception e){
                userIdPool.giveback(borrowedUserID);
                transactionManager.rollback(status);
                Log.error("createFollowee err", e);
                throw new UserServiceException(e, ExceptionCode.InternalSystemErrorException.getCode());
            }

            //add relation
            doAddRelation(followerId, followeeDO, followReason, nick, READWRITE_ACCESS_FOR_ALLFIELDS,appFrom);

        }catch(UserServiceException e){
            // own exception, re-throw
            Log.error(null, e);
            throw e;
        }catch(Throwable t){
            Log.error(null, t);
            throw new UserServiceException(t, ExceptionCode.InternalSystemErrorException.getCode());
        }
        Log.info("Followee({}) created succesfully", followeeId);
        return followeeId;
    }

    @Override
    @Deprecated
    public boolean addFollowee(Long followerId, User followee, RelationType followReason, String nick) {
        return addFollowee(followerId, followee, followReason, nick, "weixin");
    }

    @Override
    @Transactional
    public boolean addFollowee(Long followerId, User followee, RelationType followReason, String nick,String appFrom) {


        // followee must exist
        if(followee.getId() == null){
            throw new IllegalArgumentException("Followee must exist! ");
        }
        try{
            //check if followerId exist
            checkUserIdExists(followerId);

            UserRelation rel = userRelationMapper.getUserRelationByFollowerIdAndFolloweeId(followerId, followee.getId());
            if(null==rel){
                //add relation
                doAddRelation(followerId, followee, followReason, nick, READONLY_ACCESS_FOR_ALLFIELDS,appFrom);
            }else{
                return false;
            }

        }catch(UserServiceException e){
            // own exception, re-throw
            Log.error(null, e);
            throw e;
        }catch(Throwable t){
            Log.error(null, t);
            throw new UserServiceException(t, ExceptionCode.InternalSystemErrorException.getCode());
        }
        Log.info("addFollowee: followerId={}, Relationtype={} with relationNick={}, from app={}, followee={}, relation added successfully",
                followerId, followee, nick, appFrom, followee);
        return true;
    }

    @Override
    @Transactional
    public boolean cancelFollow(Long followerId, Long followeeId) {
        boolean success=false;
        try{
            //check if followerId exist
            checkUserIdExists(followerId);
            UserRelation existsRelation=userRelationMapper.getUserRelationByFollowerIdAndFolloweeId(followerId,followeeId);
            if(existsRelation==null){
                return false;
            }

            DefaultTransactionDefinition def=new DefaultTransactionDefinition();
            def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
            def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
            TransactionStatus status=transactionManager.getTransaction(def);
            try{
                userRelationMapper.deleteUserRelation(followerId, followeeId);
                transactionManager.commit(status);
                success=true;
            }catch(Exception e){
                transactionManager.rollback(status);
                Log.error(null, e);
                throw new UserServiceException(e, ExceptionCode.InternalSystemErrorException.getCode());
            }
            if(success){
                try{
                    status=transactionManager.getTransaction(def);
                    userReverseRelationMapper.deleteUserReverseRelation(followerId,followeeId);
                    transactionManager.commit(status);
                }catch(Exception e){
                    transactionManager.rollback(status);
                    Log.error(null, e);
                    throw new UserServiceException(e, ExceptionCode.InternalSystemErrorException.getCode());
                }
            }
            Log.info("cancelFollow: user({}) --> user({}), relationship cancelled.", followeeId, followeeId);
        }catch(UserServiceException e){
            // own exception, re-throw
            Log.error(null, e);
            throw e;
        }catch(Throwable t){
            Log.error(null, t);
            throw new UserServiceException(t, ExceptionCode.InternalSystemErrorException.getCode());
        }
        return success;
    }

    @Override
    public boolean updateUserRelation(Long followerId, User followee, RelationType followReason, String followeeNick) {
        if (followerId == null) {
            throw new IllegalArgumentException("followerId shouldn't be null.");
        }
        if (followee.getId() == null) {
            throw new IllegalArgumentException("followeeId shouldn't be null.");
        }
        if (followReason == null) {
            throw new IllegalArgumentException("RelationType shouldn't be null.");
        }

        // TODO[haomin]: why those code here?
        UserRelation existsRelation=userRelationMapper.getUserRelationByFollowerIdAndFolloweeId(followerId,followee.getId());
        if(existsRelation==null){
            return false;
        }else{
            User user = userMapper.getUserById(followee.getId());
            Map<String, Object> userParams = new HashMap<String, Object>();
            userParams.put("id",followee.getId());
            userParams.put("birthday",followee.getBirthday());
            userParams.put("gender",followee.getGender());
            userParams.put("height",followee.getHeight());
            userParams.put("weight",followee.getWeight());
            userMapper.updateUserById(userParams);

            Map<String, Object> params=new HashMap<String,Object>();
            params.put("followerId",followerId);
            params.put("followeeId",followee.getId());
            params.put("nick", followeeNick);
            params.put("type",followReason);
            userRelationMapper.updateUserRelation(params);
        }

        return true;
    }

    public boolean updateUserRelation(Long followerId, Long followeeId, RelationType followReason, String followeeNick){
        if (followerId == null) {
            throw new IllegalArgumentException("followerId shouldn't be null.");
        }
        if (followeeId == null) {
            throw new IllegalArgumentException("followeeId shouldn't be null.");
        }
        if (followReason == null) {
            throw new IllegalArgumentException("RelationType shouldn't be null.");
        }

        UserRelation existsRelation = null;
        existsRelation=userRelationMapper.getUserRelationByFollowerIdAndFolloweeId(followerId, followeeId);
        if(existsRelation==null){
            return false;
        }else{
            Map<String, Object> params=new HashMap<String,Object>();
            params.put("followerId",followerId);
            params.put("followeeId",followeeId);
            params.put("nick", followeeNick);
            params.put("type",followReason);
            userRelationMapper.updateUserRelation(params);
        }

        return true;
    }

    @Override
    public List<UserRelation> getFollowees(Long userId) {

        try{
            checkUserIdExists(userId);
            return userRelationMapper.getFolloweesByFollowerId(userId);
        }catch(UserServiceException e){
            // own exception, re-throw
            Log.error(null, e);
            throw e;
        }catch(Throwable t){
            Log.error(null, t);
            throw new UserServiceException(t, ExceptionCode.InternalSystemErrorException.getCode());
        }
    }

    @Override
    public UserRelation checkRelation(Long from, Long to){

        if(from == null || to == null){
            Log.info("checkRelation: input parameter can not be null!");
            return null;
        }

        UserRelation relation = null;
        try{
            relation = userRelationMapper.getUserRelationByFollowerIdAndFolloweeId(from, to);
        }catch(Throwable e){
            Log.error(null, e);
            throw new UserServiceException(e, ExceptionCode.InternalSystemErrorException.getCode());
        }

        if(relation == null){
            Log.info("checkRelation: no relation found from {} to {}", from, to);
            return null;
        }
         Log.info("checkRelation: user({}) --> user({}) has a relation with type({}), nick({})", from, to, relation.getType(), relation.getNick());
        return relation;
    }

    /*@Override
    public List<Long> getFollowers(Long userId) {
        try{
            checkUserIdExists(userId);
            return userReverseRelationMapper.getFollowerIdsByFolloweeId(userId);
        }catch(UserServiceException e){
            // own exception, re-throw
            Log.error(null, e);
            throw e;
        }catch(Throwable t){
            Log.error(null, t);
            throw new UserServiceException(t, ExceptionCode.InternalSystemErrorException.getCode());
        }
    }*/

    @Override
    public VersionedValue<String> getUserExtData(Long userId, String domain, String extKey) {
        if (userId == null) {
            throw new IllegalArgumentException("userId shouldn't be null.");
        }
        if (domain == null) {
            throw new IllegalArgumentException("domain shouldn't be null.");
        }
        if (extKey == null) {
            throw new IllegalArgumentException("extKey shouldn't be null.");
        }
        try {
            UserExtDO userExt = userExtMapper.getUserExt(userId, domain, extKey);
            if (userExt == null) {
                Log.error("UserExt not found");
                return null;
            } else {
                return new VersionedValue<String>(userExt.getVersion(), userExt.getExtData());
            }
        } catch (UserServiceException e) {
            Log.error(null, e);
            throw e;
        } catch (Throwable t) {
            Log.error(null, t);
            throw new UserServiceException(t, ExceptionCode.InternalSystemErrorException.getCode());
        }
    }

    @Override
    public Map<String, VersionedValue<String>> getUserExtAll(Long userId, String domain) {
        if (userId == null) {
            throw new IllegalArgumentException("userId shouldn't be null.");
        }
        if (domain == null) {
            throw new IllegalArgumentException("domain shouldn't be null.");
        }
        try {
            List<UserExtDO> userExtList = userExtMapper.getUserExtList(userId, domain);
            if (userExtList == null) {
                Log.error("UserExt not found");
                return null;
            } else {
                Map<String, VersionedValue<String>> res = new HashMap<String, VersionedValue<String>>();
                for (UserExtDO userExt : userExtList) {
                    res.put(userExt.getExtKey(), new VersionedValue<String>(userExt.getVersion(), userExt.getExtData()));
                }
                return res;
            }
        } catch (UserServiceException e) {
            Log.error(null, e);
            throw e;
        } catch (Throwable t) {
            Log.error(null, t);
            throw new UserServiceException(t, ExceptionCode.InternalSystemErrorException.getCode());
        }
    }

    @Override
    @Transactional
    public void setUserExtData(Long userId, String domain, String extKey, String extData, Long lastSeenVersion) {
        if (userId == null) {
            throw new IllegalArgumentException("userId shouldn't be null.");
        }
        if (domain == null) {
            throw new IllegalArgumentException("domain shouldn't be null.");
        }
        if (extKey == null) {
            throw new IllegalArgumentException("extKey shouldn't be null.");
        }
        if (extData != null && extData.length() > 4000) {
            throw new IllegalArgumentException("extData length exceeds max length of 4000 chars.");
        }
        try {
            if (lastSeenVersion == null) lastSeenVersion = 0L;
            UserExtDO possibleExistingUserExt = userExtMapper.getUserExt(userId, domain, extKey);
            if (possibleExistingUserExt != null) {
                if (!possibleExistingUserExt.getVersion().equals(lastSeenVersion)) {
                    throw new UserServiceException("Versioned UserExt value exists which differs from last seen version.", ExceptionCode.StaleDataException.getCode());
                } else {
                    Map<String, Object> model = new HashMap<String, Object>();
                    model.put("userId", userId);
                    model.put("domain", domain);
                    model.put("extKey", extKey);
                    model.put("extData", extData);
                    model.put("version", lastSeenVersion);
                    int affectedRows = userExtMapper.updateUserExt(model);
                    if (affectedRows < 1) {
                        throw new UserServiceException("Failed optimistic lock. Please re-try", ExceptionCode.StaleDataException.getCode());
                    }
                }
            } else {
                userExtMapper.insertUserExt(new UserExtDO(userId, domain, extKey, extData, lastSeenVersion + 1));
            }
        } catch (UserServiceException e) {
            // TODO log level and whether throw exception
            Log.error(null, e);
            throw e;
        } catch (Throwable t) {
            Log.error(null, t);
            throw new UserServiceException(t, ExceptionCode.InternalSystemErrorException.getCode());
        }
    }

    @Override
    @Transactional
    public void removeUserExtData(Long userId, String domain, String extKey, Long lastSeenVersion) {
        if (userId == null) {
            throw new IllegalArgumentException("userId shouldn't be null.");
        }
        if (domain == null) {
            throw new IllegalArgumentException("domain shouldn't be null.");
        }
        if (extKey == null) {
            throw new IllegalArgumentException("extKey shouldn't be null.");
        }
        try {
            UserExtDO possibleExistingUserExt = userExtMapper.getUserExt(userId, domain, extKey);
            if (possibleExistingUserExt != null) {
                if (!possibleExistingUserExt.getVersion().equals(lastSeenVersion)) {
                    throw new UserServiceException("Versioned UserExt value exists which differs from last seen version.", ExceptionCode.StaleDataException.getCode());
                } else {
                    int affected = userExtMapper.deleteUserExt(userId, domain, extKey, lastSeenVersion);
                    if (affected < 1) {
                        throw new UserServiceException("No matching record to remove.", ExceptionCode.StaleDataException.getCode());
                    }
                }
            }
        } catch (UserServiceException e) {
            Log.error(null, e);
            throw e;
        } catch (Throwable t) {
            Log.error(null, t);
            throw new UserServiceException(t, ExceptionCode.InternalSystemErrorException.getCode());
        }
    }


    /**
     *
     * 注意：此函数中param的key字符串必须与UserMapper.xml中updateUserByUserId中的marker字符串一一匹配
     *
     * @param changes
     * @return
     */
    private Map<String, Object> convert(User changes){
        Map<String, Object> params = new HashMap<String, Object>();

        if(changes.getBirthday() != null){
            params.put("birthday", changes.getBirthday());
        }
        if(changes.getBloodType() != null){
            params.put("bloodType", changes.getBloodType());
        }
        if(changes.getCertId() != null){
            params.put("certId", changes.getCertId());
        }
        if(changes.getAvatar() != null){
            params.put("avatar", changes.getAvatar());
        }
        if(changes.getCertType() != null){
            params.put("certType", changes.getCertType());
        }
        if(changes.getHeight() != null){
            params.put("height", changes.getHeight());
        }
        if(changes.getPassword() != null){
            params.put("password", changes.getPassword());
        }
        if(changes.getPhysique() != null){
            params.put("physique", changes.getPhysique());
        }
        if(changes.getRole() != null){
            params.put("role", changes.getRole());
        }
        if(changes.getWeight() != null){
            params.put("weight", changes.getWeight());
        }
        if(changes.getWanliId() != null){
            params.put("wanliId", changes.getWanliId());
        }
        if(changes.getYizhangId() != null){
            params.put("yizhangId", changes.getYizhangId());
        }
        if(changes.getMobile() != null){
            params.put("mobile", changes.getMobile());
        }
        if(changes.getNick() != null){
            params.put("nick", changes.getNick());
        }
        if(changes.getGender() != null){
            params.put("gender", changes.getGender());
        }
        return params;
    }

    private Boolean checkChannelUniqueness(List<AccountChannelDO> accounts, Set<ChannelType> counter){
        for(AccountChannelDO acc : accounts){
            if(counter.contains(acc.getFrom())){
                return false;
            }
            counter.add(acc.getFrom());
        }

        return true;
    }

    private void doAddRelation(Long followerId, User followee, RelationType followReason, String nick, String access,String appFrom) {
        boolean success=false;
        UserRelation rel = new UserRelation();
        rel.setFollowerId(followerId);
        rel.setFolloweeId(followee.getId());
        rel.setType(followReason);
        rel.setNick(nick);
        rel.setAccess(access);
        rel.setAppFrom(appFrom);
        DefaultTransactionDefinition def=new DefaultTransactionDefinition();
        def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        TransactionStatus status=transactionManager.getTransaction(def);
        try{
            userRelationMapper.insertUserRelation(rel);
            transactionManager.commit(status);
           success=true;
        }catch(Exception e){
            transactionManager.rollback(status);
            Log.error(null, e);
            throw new UserServiceException(e, ExceptionCode.InternalSystemErrorException.getCode());
        }
        if(success){
            status=transactionManager.getTransaction(def);
            try{
                UserRelation reverseRelation=userReverseRelationMapper.getUserReverseRelationByFollowerIdAndFolloweeId(rel.getFollowerId(),rel.getFolloweeId());
                if(null==reverseRelation){
                    userReverseRelationIndex.put("com.pajk.user.dal.mapper.UserReverseRelationMapper.insertUserReverseRelation",rel);
                    transactionManager.commit(status);
                }
            }catch(Exception e){
                transactionManager.rollback(status);
                Log.error(null, e);
                throw new UserServiceException(e, ExceptionCode.InternalSystemErrorException.getCode());
            }

        }
    }

    private void checkUserIdExists(Long userId) {
//        User user = userMapper.getUserById(userId);
//        if(user == null){
//            throw new UserServiceException(String.format("UserID(%d) does not exist!", userId),
//                                           ExceptionCode.EntityNotFoundException.getCode());
//        }
    }

	@Override
	public int getUserDeviceBindingState(Long deviceId, Long userId) {
		if(userId == 1234567890909L) return 2;
		if(deviceId == null || userId == null) throw new IllegalArgumentException("deviceId or userId shouldn't be null.");
		UserDeviceBindingDO userDeviceBindingDO = userDeviceBindingMapper.getUserDeviceBindingByUserIdAndDeviceId(userId, deviceId);
		if(userDeviceBindingDO != null) {
			if(userDeviceBindingDO.getActive() == 0) {
				return 2;
			}
			return 1;
		}
		User user = userMapper.getUserById(userId);
		if(user == null) {
			Log.error("user cannot be null,userId is {}",userId);
			return -1;
		}
		if(user.getBirthday() == null && user.getBloodType() == null && user.getHeight() == null 
				&& user.getGender() == null && StringUtil.isEmpty(user.getNick())) {
			List<UserRelation> userRelations = userRelationMapper.getFolloweesByFollowerIdAndType(user.getId(), null);
			if(userRelations == null || userRelations.size() == 0) {
				return 2;
			}
		}
		return -1;
	}

	@Override
	public CallerInfo getUserInfoBySmsPass(Long deviceId, Long mobile, String smsPass) {
		if(deviceId == null || mobile == null || StringUtil.isEmpty(smsPass)) throw new IllegalArgumentException("deviceId or mobile or smsPass shouldn't be null.");
		boolean result = smsPasswordManager.verifySmsPassword(mobile.toString(), smsPass);
		if(!result) {
			if(Pattern.matches("^"+smsPasswordManager.UP+"\\d{6}$", smsPass)) {
				DubboExtProperty.setErrorCode(UserServiceHttpCode.UPLINK_SMS_NOT_RECEIVED); 
			}else {
				DubboExtProperty.setErrorCode(UserServiceHttpCode.DYNAMIC_CODE_ERROR); 
			}
			return null;
		}
		
		Long userId = mobileToUserIdIndex.get((long)mobile);
		if(userId == null) {
			Log.error("userId is null,mobile is {}",mobile);
			DubboExtProperty.setErrorCode(UserServiceHttpCode.MOBILE_NOT_REGIST);
			return null;
		}
		
		UserDeviceBindingDO userDeviceBindingDO = userDeviceBindingMapper.getUserDeviceBindingByUserIdAndDeviceId(userId, deviceId);
		CallerInfo callerInfo = new CallerInfo();
		callerInfo.uid = userId;
		callerInfo.deviceId = deviceId;
		if(userDeviceBindingDO == null) {
			User user = userMapper.getUserById(userId);
			if(user == null) {
				Log.error("user cannot be null,userId is {}",userId);
			}
			if(user.getBirthday() == null && user.getBloodType() == null && user.getHeight() == null 
					&& user.getGender() == null && StringUtil.isEmpty(user.getNick())) {
				List<UserRelation> userRelations = userRelationMapper.getFolloweesByFollowerIdAndType(user.getId(), null);
				if(userRelations == null || userRelations.size() == 0) {
					return callerInfo;
				}
			}
			DubboExtProperty.setErrorCode(UserServiceHttpCode.NO_TRUSTED_DEVICE); 
			return null;
		}
		callerInfo.appid = Integer.valueOf(userDeviceBindingDO.getAppId());
		return callerInfo;
	}

	@Override
	public void mergeUser(Long oldUserId, Long newUserId) {
		//TODO:transaction control
		SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH,false);
		 try{
			List<UserRelation> userRelations = userRelationMapper.getFolloweesByFollowerId(oldUserId);
			User oldUser = userMapper.getUserById(oldUserId);
			User changes = new User();
			changes.setBirthday(oldUser.getBirthday());
			changes.setBloodType(oldUser.getBloodType());
			changes.setGender(oldUser.getGender());
			changes.setCertId(oldUser.getCertId());
			changes.setCertType(oldUser.getCertType());
			changes.setHeight(oldUser.getHeight());
			changes.setWeight(oldUser.getWeight());
			Map<String, Object> params = convert(changes);
			params.put("id", newUserId);
			userMapper.updateUserById(params);
			User u =userMapper.getUserById(newUserId);
			userRelationMapper.deleteUserRelationByFollowerId(oldUserId);
			UserRelationMapper mapper = sqlSession
					.getMapper(UserRelationMapper.class);
			for (UserRelation e : userRelations) {
                userReverseRelationMapper.deleteUserReverseRelation(e.getFollowerId(),e.getFolloweeId());
				e.setFollowerId(newUserId);
				mapper.insertUserRelation(e);
			}
			sqlSession.commit();
			
    		
	        }catch(Exception e){
	            // own exception, re-throw
	        	Log.error(null, e);
	            throw new UserServiceException(e, ExceptionCode.InternalSystemErrorException.getCode());
	        }finally {
	        	sqlSession.close();
	        }
		
	}

    @Override
    public void inviteUser(Long inviterUserId, String inviteeMobile, List<InviteeFolloweeInfo> inviteeFolloweeInfoList,String appFrom,String messageTag,String businessBody)    {
        try{
            HashMap<Long,InviteInfo> inviteInfoMap=inviteeMobileToInviteInfoIndex.get(inviteeMobile);
            if(inviteInfoMap==null){
                inviteInfoMap=new HashMap<Long,InviteInfo>();
            }

            InviteInfo inviteInfo  =new InviteInfo();
            inviteInfo.setInviterUserId(inviterUserId);
            inviteInfo.setInviteeMobile(inviteeMobile);
            inviteInfo.setInviteeFolloweeInfoList(inviteeFolloweeInfoList);
            inviteInfo.setAppFrom(appFrom);
            inviteInfo.setBusinessBody(businessBody);
            inviteInfo.setMessageTag(messageTag);
            inviteInfoMap.put(inviterUserId,inviteInfo);

            List<String> inviteList= inviteListIndex.get(inviterUserId);
            if(inviteList==null || inviteList.size()==0 ){
                inviteList=new ArrayList<String>();
            }
            inviteList.add(inviteeMobile);
            inviteeMobileToInviteInfoIndex.put(inviteeMobile,inviteInfoMap);
            //存用户所有邀请人手机号
            inviteListIndex.put(inviterUserId,inviteList);
            Log.info("inviteUser: inviterUserId {} inviteeMobile {}  appFrom {} inviteeFolloweeInfoList {} businessBody {} ",
                    new Object[]{inviterUserId,inviteeMobile,appFrom,inviteeFolloweeInfoList,businessBody});
        } catch (UserServiceException e) {
            Log.error(null, e);
            throw e;
        } catch (Throwable t) {
            Log.error(null, t);
            throw new UserServiceException(t, ExceptionCode.InternalSystemErrorException.getCode());
        }
    }
    
    public void setUserIdPool(IDPool userIdPool) {
        this.userIdPool = userIdPool;
    }
}
